// --------------------------------------------------------------------------------------------------------------------- 
// <copyright file="LinearRegression.cs" company="Scrum for Team System">
//   None
// </copyright>
// <summary>
//   Defines the LinearRegression type.
// </summary>
// ---------------------------------------------------------------------------------------------------------------------
namespace ScrumForTeamSystem.Metrics
{
    /// <summary>
    /// Linear Regression ModelBase, which attempts to approximate the set of Data points by a straight line. Unlike the moving average models below, which can plot Trend points as they traverse the Data points, this model needs all the Data points up front. Since the Data points are supplied by Reporting Services one-by-one via the GetNextTrendValue method, the Trend is only drawn from the end of the Data points.
    /// </summary>
    internal class LinearRegression : ModelBase
    {
        /// <summary>
        /// Gets or sets the Y intercept.
        /// </summary>
        /// <value>The Y intercept.</value>
        protected double YIntercept { get; set; }

        /// <summary>
        /// Gets or sets the slope.
        /// </summary>
        /// <value>The slope.</value>
        protected double Slope { get; set; }

        /// <summary>
        /// Overrides the base class to calculate a Trend line point given a Data point. Since the linear regression cannot be calculated until all Data points have been received, returns null until the last Data point has been seen.
        /// </summary>
        /// <returns>
        /// A Trend line point, or null.
        /// </returns>
        public override double? GetNextTrendValue()
        {
            return this.YIntercept + (this.Slope * this.PointCount++) - this.Adjustment;
        }

        /// <summary>
        /// Calculates the Linear Regression.
        /// </summary>
        protected override void CalculateTrend()
        {
            var last = this.PointCount - 1;
            this.CalculateRegression();
            this.Adjustment = this.IsAdjusted 
                                      ? this.YIntercept + (this.Slope * last) - (double)this.Data[last] 
                                      : 0;
        }

        /// <summary>
        /// Calculates the Slope and YIntercept for the linear regression.
        /// </summary>
        protected void CalculateRegression()
        {
            // e.g. if there are 12 points, numbered 0-11, the mean is 5.5 double ymean = MeanOfY();
            var xmean = (this.PointCount - 1) / 2.0;
            var ymean = this.MeanOfY();
            var xvariance = this.VarianceOfX(xmean);
            var covariance = this.Covariance(xmean, ymean);

            this.Slope = covariance / xvariance;
            this.YIntercept = ymean - (this.Slope * xmean);
        }

        /// <summary>
        /// Gets the Covariance of X and Y, i.e. how much they vary together. Calculates the average of the products of the distance of the X and Y-values from their mean values.
        /// </summary>
        /// <param name="xmean">The mean of the X-values.</param>
        /// <param name="ymean">The mean of the Y-values.</param>
        /// <returns>
        /// The Covariance.
        /// </returns>
        private double Covariance(double xmean, double ymean)
        {
            double total = 0;
            for (int i = 0; i < this.PointCount; i++)
            {
                double diffx = i - xmean;
                double diffy = (double)this.Data[i] - ymean;
                total += diffx * diffy;
            }

            return total / this.PointCount;
        }

        /// <summary>
        /// Gets the mean of all the Y-values.
        /// </summary>
        /// <returns>The mean value.</returns>
        private double MeanOfY()
        {
            double total = 0;
            for (int i = 0; i < this.PointCount; i++)
            {
                total += (double)this.Data[i];
            }

            return total / this.PointCount;
        }

        /// <summary>
        /// Gets the Variance of all the X-values, i.e. the average of the squares of the distances of the points from the X-mean.
        /// </summary>
        /// <param name="xmean">The mean of all the X-values.</param>
        /// <returns>The X-variance.</returns>
        private double VarianceOfX(double xmean)
        {
            double total = 0;
            for (int i = 0; i < this.PointCount; i++)
            {
                double diff = i - xmean;
                total += diff * diff;
            }

            return total / this.PointCount;
        }
    }
}